<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="generator" content="rustdoc">
    <title>Preview PineTime Watch Faces in your Web Browser with WebAssembly</title>

    
    <!-- Begin scripts/articles/*-header.html: Article Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<meta property="og:title" 
    content="Preview PineTime Watch Faces in your Web Browser with WebAssembly" 
    data-rh="true">
<meta property="og:description" 
    content="How we build and preview PineTime Watch Faces with only a web browser... No computer needed!" 
    data-rh="true">
<meta property="og:image" 
    content="https://lupyuen.github.io/images/pinetime-simulator.png">
<meta property="og:type" 
    content="article" data-rh="true">
<!-- End scripts/articles/*-header.html -->
<!-- Begin scripts/rustdoc-header.html: Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<link rel="alternate" type="application/rss+xml" title="RSS Feed for lupyuen" href="/rss.xml" />
<link rel="stylesheet" type="text/css" href="../normalize.css">
<link rel="stylesheet" type="text/css" href="../rustdoc.css" id="mainThemeStyle">
<link rel="stylesheet" type="text/css" href="../dark.css">
<link rel="stylesheet" type="text/css" href="../light.css" id="themeStyle">
<link rel="stylesheet" type="text/css" href="../prism.css">
<script src="../storage.js"></script><noscript>
<link rel="stylesheet" href="../noscript.css"></noscript>
<link rel="shortcut icon" href="../favicon.ico">
<style type="text/css">
    #crate-search {
        background-image: url("../down-arrow.svg");
    }
    a {
        color: #77d;
    }
</style>
<!-- End scripts/rustdoc-header.html -->


</head>
<body class="rustdoc">
    <!--[if lte IE 8]>
    <div class="warning">
        This old browser is unsupported and will most likely display funky
        things.
    </div>
    <![endif]-->

        <!-- Begin scripts/rustdoc-before.html: Pre-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->

    <!-- Begin Theme Picker -->
    <div class="theme-picker" style="left: 0"><button id="theme-picker" aria-label="Pick another theme!"><img src="../brush.svg"
        width="18" alt="Pick another theme!"></button>
        <div id="theme-choices"></div>
    </div>
    <script src="../theme.js"></script>
    <script src="../prism.js"></script>
    <!-- Theme Picker -->

    <!-- End scripts/rustdoc-before.html -->
    

    <h1 class="title">Preview PineTime Watch Faces in your Web Browser with WebAssembly</h1>
    <nav id="TOC"><ul>
<li><a href="#create-a-fork-of-pinetime-source-files">1 Create a Fork of PineTime Source Files</a><ul></ul></li>
<li><a href="#enable-github-pages">2 Enable GitHub Pages</a><ul></ul></li>
<li><a href="#add-github-actions-to-our-fork">3 Add GitHub Actions to our Fork</a><ul></ul></li>
<li><a href="#modify-the-pinetime-source-code">4 Modify the PineTime Source Code</a><ul></ul></li>
<li><a href="#our-first-pinetime-simulator-build">5 Our First PineTime Simulator Build</a><ul></ul></li>
<li><a href="#preview-our-pinetime-watch-face">6 Preview our PineTime Watch Face</a><ul></ul></li>
<li><a href="#other-options">7 Other Options</a><ul></ul></li>
<li><a href="#whats-next">8 What's Next?</a><ul></ul></li>
<li><a href="#how-it-works">9 How It Works</a><ul>
<li><a href="#checkout-source-files">9.1 Checkout Source Files</a><ul></ul></li>
<li><a href="#check-cache-for-emscripten">9.2 Check Cache for emscripten</a><ul></ul></li>
<li><a href="#install-emscripten">9.3 Install emscripten</a><ul></ul></li>
<li><a href="#check-cache-for-wabt">9.4 Check Cache for wabt</a><ul></ul></li>
<li><a href="#install-wabt">9.5 Install wabt</a><ul></ul></li>
<li><a href="#checkout-lvgl-for-webassembly">9.6 Checkout LVGL for WebAssembly</a><ul></ul></li>
<li><a href="#copy-watch-face-clockcpp-to-lvgl-for-webassembly">9.7 Copy Watch Face Clock.cpp to LVGL for WebAssembly</a><ul></ul></li>
<li><a href="#build-lvgl-for-webassembly">9.8 Build LVGL for WebAssembly</a><ul></ul></li>
<li><a href="#copy-webassembly-to-github-pages">9.9 Copy WebAssembly to GitHub Pages</a><ul></ul></li>
<li><a href="#commit-github-pages">9.10 Commit GitHub Pages</a><ul></ul></li>
<li><a href="#upload-outputs">9.11 Upload Outputs</a><ul></ul></li>
<li><a href="#show-files">9.12 Show Files</a><ul></ul></li>
<li><a href="#caching-at-the-end">9.13 Caching At The End</a><ul></ul></li></ul></li>
<li><a href="#environment-variables">10 Environment Variables</a><ul></ul></li></ul></nav><p><img src="https://lupyuen.github.io/images/pinetime-simulator.png" alt="Custom PineTime Watch Face created in C++ by SravanSenthiln1: PineTime Watch Face Simulator vs Real PineTime" /></p>
<p><em>Custom PineTime Watch Face created in C++ by <a href="https://twitter.com/SravanSenthiln1">SravanSenthiln1</a>: PineTime Watch Face Simulator vs Real PineTime</em></p>
<p>Now we can build and preview Watch Faces for <a href="https://wiki.pine64.org/index.php/PineTime"><strong>PineTime Smart Watch</strong></a> in the Web Browser, thanks to WebAssembly...</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/pinetime-lab/lvgl.html">Online Demo</a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/pinetime-lab/blob/master/src/DisplayApp/Screens/Clock.cpp">Watch Face Source Code in C++</a></p>
</li>
</ul>
<p><strong>All we need is a Web Browser</strong>... Even a Web Browser on mobile phones will do!</p>
<p>Let's learn how...</p>
<h1 id="create-a-fork-of-pinetime-source-files" class="section-header"><a href="#create-a-fork-of-pinetime-source-files">1 Create a Fork of PineTime Source Files</a></h1>
<p><em>(Nope no knife!)</em></p>
<ol>
<li>
<p><strong>Create a free GitHub Account</strong> if we haven't got one...</p>
<p>▶️   <a href="https://github.com/"><code>github.com</code></a></p>
</li>
<li>
<p><strong>Browse to the GitHub Repository</strong> for the PineTime Firmware...</p>
<p>▶️   <a href="https://github.com/JF002/Pinetime"><code>github.com/JF002/Pinetime</code></a></p>
<p>Here's the complete Source Code for the InfiniTime Firmware (based on FreeRTOS).</p>
</li>
<li>
<p>Click the <code>Fork</code> button at top right...</p>
<p><img src="https://lupyuen.github.io/images/cloud-fork.png" alt="Create a fork" /></p>
</li>
<li>
<p>This creates a <strong>Fork</strong> of the PineTime Repository under our GitHub Account...</p>
<p><img src="https://lupyuen.github.io/images/cloud-fork2.png" alt="Created the fork" /></p>
<p>The URL looks like this...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="ident">https</span>:<span class="comment">//github.com/ACCOUNT_NAME/Pinetime</span></pre></div>
</li>
<li>
<p>The Fork contains <strong>our own copy</strong> of the entire Source Code for the PineTime Firmware... Ready for us to make any updates!</p>
<p>GitHub helpfully <strong>tracks updates to our Fork,</strong> so that one day we may submit a <strong>Pull Request</strong> to sync our updates (only the useful ones) back to the original PineTime Repository.</p>
<p>And we may also <strong>Pull Updates</strong> from the original PineTime Repository and apply them to our Fork.</p>
<p>That's how we maintain Open Source Projects!</p>
</li>
</ol>
<p>Read on to learn how we add GitHub Actions to our Fork to preview our Custom Watch Face automagically...</p>
<h1 id="enable-github-pages" class="section-header"><a href="#enable-github-pages">2 Enable GitHub Pages</a></h1>
<ol>
<li>
<p>In our Fork on GitHub, click <code>...</code> and <code>Settings</code> at top right</p>
<p><img src="https://lupyuen.github.io/images/simulator-settings.png" alt="GitHub Settings" /></p>
</li>
<li>
<p>Scroll down the <code>Settings</code> page (<code>Options</code> tab) and look for <strong>GitHub Pages</strong></p>
</li>
<li>
<p>Set <code>Branch</code> to <code>master</code></p>
<p>Set the folder to <code>docs</code></p>
<p>Click <code>Save</code></p>
<p><img src="https://lupyuen.github.io/images/simulator-pages.png" alt="GitHub Pages" /></p>
</li>
</ol>
<h1 id="add-github-actions-to-our-fork" class="section-header"><a href="#add-github-actions-to-our-fork">3 Add GitHub Actions to our Fork</a></h1>
<ol>
<li>
<p>In our Fork on GitHub, click <code>Actions</code> at the top bar</p>
<p><img src="https://lupyuen.github.io/images/cloud-actions.png" alt="GitHub Actions" /></p>
</li>
<li>
<p>Click <code>Skip this and set up a workflow yourself</code></p>
<p><img src="https://lupyuen.github.io/images/cloud-actions2.png" alt="GitHub Actions" /></p>
</li>
<li>
<p>GitHub brings us to a page to edit <code>.github/workflows/main.yml</code></p>
<p><img src="https://lupyuen.github.io/images/cloud-actions3.png" alt="GitHub Actions" /></p>
</li>
<li>
<p>Change <code>main.yml</code> to <code>simulate.yml</code></p>
<p><img src="https://lupyuen.github.io/images/simulator-rename.png" alt="Rename to main.yml to simulate.yml Pages" /></p>
</li>
<li>
<p>Open a new web browser tab. </p>
<p>Browse to this page...</p>
<p><a href="https://raw.githubusercontent.com/lupyuen/pinetime-lab/master/.github/workflows/simulate.yml"><code>github.com/lupyuen/pinetime-lab/.github/workflows/simulate.yml</code></a></p>
<p>Copy the contents of this page. </p>
</li>
<li>
<p>Switch back to the earlier page: <code>.github/workflows/simulate.yml</code></p>
<p>Paste and overwrite the contents of the file...</p>
<p><img src="https://lupyuen.github.io/images/simulator-actions.png" alt="GitHub Actions" /></p>
</li>
<li>
<p>Click <code>Start Commit</code> at the right or bottom of the page...</p>
<p><img src="https://lupyuen.github.io/images/cloud-actions5.png" alt="GitHub Actions" /></p>
</li>
<li>
<p>Click <code>Commit New File</code></p>
<p><img src="https://lupyuen.github.io/images/simulator-actions2.png" alt="GitHub Actions" /></p>
</li>
</ol>
<p>We have just created a <strong>Workflow</strong>... An automated job that will be run by GitHub whenever we update our source files.</p>
<p>If we ever need to edit the Workflow, just browse to this URL...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="ident">https</span>:<span class="comment">//github.com/ACCOUNT_NAME/Pinetime/blob/master/.github/workflows/simulate.yml</span></pre></div>
<p>(Change <code>ACCOUNT_NAME</code> to our GitHub Account Name)</p>
<p>Let's change a PineTime source file... And trigger our very first PineTime Simulator Build in the Cloud!</p>
<h1 id="modify-the-pinetime-source-code" class="section-header"><a href="#modify-the-pinetime-source-code">4 Modify the PineTime Source Code</a></h1>
<p>We shall modify the source code so that the PineTime Watch Face shows our own special message...</p>
<ol>
<li>
<p>Browse to this URL...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="ident">https</span>:<span class="comment">//github.com/ACCOUNT_NAME/Pinetime/blob/master/src/DisplayApp/Screens/Clock.cpp</span></pre></div>
<p>(Change <code>ACCOUNT_NAME</code> to our GitHub Account Name)</p>
</li>
<li>
<p>Click the <code>Edit</code> icon at the right...</p>
<p><img src="https://lupyuen.github.io/images/cloud-edit.png" alt="Edit Source File" /></p>
</li>
<li>
<p>Look for the line with <code>&quot;BPM&quot;</code> (line 71)...</p>
<p><img src="https://lupyuen.github.io/images/cloud-edit2.png" alt="Edit Source File" /></p>
</li>
<li>
<p><code>BPM</code> is the text that's displayed on the PineTime Watch Face.</p>
<p>Change <code>BPM</code> to our own short message, like <code>LOVE</code>...</p>
<p><img src="https://lupyuen.github.io/images/cloud-edit3.png" alt="Edit Source File" /></p>
</li>
<li>
<p>Scroll to the bottom of the page.</p>
<p>Click <code>Commit Changes</code></p>
<p><img src="https://lupyuen.github.io/images/cloud-edit4.png" alt="Edit Source File" /></p>
</li>
</ol>
<p>Guess what?</p>
<p>We have just triggered <strong>Our Very First PineTime Simulator Build In The Cloud!</strong></p>
<p>(Because the Simulator Build is triggered by any file update)</p>
<p>Let's check the result of our Simulator Build...</p>
<p><a href="https://wiki.pine64.org/index.php?title=PineTime_Custom_Watchface_Tutorial">Check out this article to learn more about Clock.cpp</a></p>
<h1 id="our-first-pinetime-simulator-build" class="section-header"><a href="#our-first-pinetime-simulator-build">5 Our First PineTime Simulator Build</a></h1>
<ol>
<li>
<p>Click <code>Actions</code> at the top.</p>
<p>Click the first row that says: <code>Update Clock.cpp - Simulate PineTime Firmware</code></p>
<p>(Make sure it's <code>Simulate PineTime Firmware</code> not <code>Build PineTime Firmware</code>)</p>
<p><img src="https://lupyuen.github.io/images/simulator-result.png" alt="Build Result" /></p>
</li>
<li>
<p>Click <code>build</code> at left...</p>
<p><img src="https://lupyuen.github.io/images/simulator-result2.png" alt="Build Result" /></p>
</li>
<li>
<p>We'll see each step of the simulator building process...</p>
<p><img src="https://lupyuen.github.io/images/simulator-result3.png" alt="Build Result" /></p>
</li>
<li>
<p>Click <code>Build LVGL for WebAssembly</code></p>
<p>This shows the messages that were generated by the WebAssembly Compiler (emscripten)...</p>
<p><img src="https://lupyuen.github.io/images/simulator-result4.png" alt="Build Result" /></p>
</li>
</ol>
<p><a href="https://github.com/lupyuen/pinetime-lab/actions?query=workflow%3A%22Build+PineTime+Firmware%22">Check out my build logs</a></p>
<h1 id="preview-our-pinetime-watch-face" class="section-header"><a href="#preview-our-pinetime-watch-face">6 Preview our PineTime Watch Face</a></h1>
<ol>
<li>
<p>On our computer or mobile phone, launch the Web Browser.</p>
</li>
<li>
<p>Browse to this URL to see PineTime Simulator for our Fork...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="ident">https</span>:<span class="comment">//ACCOUNT_NAME.github.io/Pinetime</span></pre></div>
<p>(Change <code>ACCOUNT_NAME</code> to our GitHub Account Name)</p>
<p><img src="https://lupyuen.github.io/images/simulator-love.png" alt="PineTime Watch Face Simulator" /></p>
</li>
<li>
<p>If we are happy with the Watch Face, we may <strong>flash the built firmware</strong> to PineTime over Bluetooth. See <a href="https://lupyuen.github.io/pinetime-rust-mynewt/articles/cloud#download-and-test-our-pinetime-firmware">&quot;Test Our PineTime Fimware&quot;</a></p>
<p>We will need to install the GitHub Actions Workflow for building the PineTime Firmware: <a href="https://github.com/lupyuen/pinetime-lab/blob/master/.github/workflows/main.yml"><code>.github/workflows/main.yml</code></a>. See <a href="https://lupyuen.github.io/pinetime-rust-mynewt/articles/cloud#add-github-actions-to-our-fork">&quot;Add GitHub Actions for PineTime Firmware&quot;</a></p>
</li>
<li>
<p>Get Creative with Watch Faces! <a href="https://wiki.pine64.org/index.php?title=PineTime_Custom_Watchface_Tutorial">Find out how to add our own Bitmaps to Watch Faces</a></p>
<p><img src="https://lupyuen.github.io/images/pinetime-simulator.png" alt="Custom PineTime Watch Face created in C++ by SravanSenthiln1: PineTime Watch Face Simulator vs Real PineTime" /></p>
<p><a href="https://github.com/AppKaki/lvgl-wasm/blob/master/clock/Clock2.cpp">View the Source Code for the Custom Watch Face</a></p>
</li>
</ol>
<p><em>I have a request...</em></p>
<p>If you could... With your kind permission... Please post to Twitter and/or Mastodon a pic of your PineTime Simulator with the new Watch Face.</p>
<p>Tag the post with <code>#PineTime</code> so we know that simulating PineTime Firmware in the Cloud works OK for you. Thanks! :-)</p>
<p>If you're stuck, please chat with us in the PineTime Chatroom...</p>
<p><a href="https://wiki.pine64.org/index.php/PineTime#Community">PineTime Chatroom on Discord / Matrix / Telegram / IRC</a></p>
<p><img src="https://lupyuen.github.io/images/simulator-morning.jpg" alt="Custom PineTime Watch Face by SravanSenthiln1" /></p>
<p><em>Custom PineTime Watch Face by <a href="https://twitter.com/SravanSenthiln1">SravanSenthiln1</a></em></p>
<h1 id="other-options" class="section-header"><a href="#other-options">7 Other Options</a></h1>
<ol>
<li>
<p><em>Can we change other files besides <code>Clock.cpp</code>?</em></p>
<p>Sorry, the Simulator only renders the code in <code>Clock.cpp</code></p>
<p>Any bitmaps and fonts will have to be embedded inside that file.</p>
</li>
<li>
<p><em>Can we edit our files in GitHub without using the web browser?</em></p>
<p>We recommend <a href="https://code.visualstudio.com/"><strong>VSCode</strong></a> or <a href="https://vscodium.com/"><strong>VSCodium</strong></a> for editing files with <a href="https://code.visualstudio.com/docs/editor/versioncontrol">Git Version Control</a>. (Which works with GitHub files)</p>
<p>Remember to <a href="https://code.visualstudio.com/docs/editor/versioncontrol#_commit">Commit any updated files</a> and <a href="https://code.visualstudio.com/docs/editor/versioncontrol#_remotes">Push the Commits</a> to the <code>master</code> Branch to trigger the simulator build.</p>
<p>After building the simulator, we need to Pull from our Fork to fetch the updated WebAssembly files in the <code>docs</code> folder.</p>
</li>
<li>
<p><em>Can we build the simulator on our own computers?</em></p>
<p>Follow the instructions in <a href="https://github.com/AppKaki/lvgl-wasm/blob/master/README.md">LVGL WebAssembly doc</a>.</p>
<p>To troubleshoot the build, compare with <a href="https://github.com/lupyuen/pinetime-lab/actions?query=workflow%3A%22Simulate+PineTime+Firmware%22">my build logs</a>.</p>
</li>
<li>
<p><em>What if we don't wish to make our repos public?</em></p>
<p>Only public repos get GitHub Actions for free... But there's an alternative:</p>
<p><a href="https://docs.github.com/en/actions/hosting-your-own-runners">Self-Hosted Runners for GitHub Actions</a></p>
</li>
</ol>
<p><img src="https://lupyuen.github.io/images/simulator-tv.jpg" alt="Custom PineTime Watch Face by SravanSenthiln1 rendered by a TV's built-in web browser" /></p>
<p><em>Custom PineTime Watch Face by <a href="https://twitter.com/SravanSenthiln1">SravanSenthiln1</a> rendered by a TV's built-in web browser</em></p>
<h1 id="whats-next" class="section-header"><a href="#whats-next">8 What's Next?</a></h1>
<p>Here's what we'll be implementing next...</p>
<ol>
<li>
<p><strong>Accept Touch Input</strong> for LVGL</p>
</li>
<li>
<p><strong>Convert <code>Clock.cpp</code> from C++ to Rust</strong> with <a href="https://github.com/rafaelcaricio/lvgl-rs"><code>lvgl-rs</code></a></p>
<p><a href="https://github.com/AppKaki/lvgl-wasm/blob/rust/README.md">Check out the <code>rust</code> branch of <code>lvgl-asm</code></a></p>
</li>
<li>
<p>Allow PineTime Watch Faces to be <strong>built online in Rust with online preview</strong>. Similar to <a href="https://webassembly.studio/">WebAssembly Studio</a></p>
</li>
</ol>
<p>We have a lot to do, please chat with us if you're keen to help...</p>
<p><a href="https://wiki.pine64.org/index.php/PineTime#Community">PineTime Chatroom on Discord / Matrix / Telegram / IRC</a></p>
<p>And remember to enjoy your PineTime :-)</p>
<p><a href="https://lupyuen.github.io/rss.xml">Check out my RSS Feed</a></p>
<h1 id="how-it-works" class="section-header"><a href="#how-it-works">9 How It Works</a></h1>
<p>(Warning: The topics below are deeply technical... If you're keen please read on!)</p>
<p>Let's look at the GitHub Actions Workflow we used for previewing PineTime Watch Faces: <a href="https://github.com/lupyuen/pinetime-lab/blob/master/.github/workflows/simulate.yml"><code>.github/workflows/simulate.yml</code></a></p>
<pre><code class="language-yaml"># GitHub Actions Workflow to build PineTime Watch Face Simulator with LVGL and WebAssembly
# See https://github.com/AppKaki/lvgl-wasm/blob/master/README.md 
# and https://lupyuen.github.io/pinetime-rust-mynewt/articles/cloud

# Name of this Workflow
name: Simulate PineTime Firmware

# When to run this Workflow...
on:

  # Run this Workflow when files are updated (Pushed) in the &quot;master&quot; Branch
  push:
    branches: [ master ]
    
  # Also run this Workflow when a Pull Request is created or updated in the &quot;master&quot; Branch
  pull_request:
    branches: [ master ]
</code></pre>
<p>Here we see the conditions that will trigger our Workflow...</p>
<ol>
<li>
<p>When files are <strong>updated (or Pushed)</strong> in the <code>master</code> Branch</p>
</li>
<li>
<p>When a <strong>Pull Request is created or updated</strong> in the <code>master</code> Branch</p>
</li>
</ol>
<p><a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request">More details</a></p>
<p>Next we specify which Operating System GitHub should use to execute the Workflow Steps...</p>
<pre><code class="language-yaml"># Steps to run for the Workflow
jobs:
  build:

    # Run these steps on Ubuntu
    runs-on: ubuntu-latest

    steps:
      ...
</code></pre>
<p>This asks GitHub to allocate a free Virtual Machine (Docker Container) to build our firmware, based on Ubuntu 18.04.</p>
<p>We're using Ubuntu, but GitHub supports Windows and macOS as well.</p>
<p><a href="https://docs.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners">More details</a></p>
<p>After that we specify the steps to be executed for our Workflow...</p>
<h2 id="checkout-source-files" class="section-header"><a href="#checkout-source-files">9.1 Checkout Source Files</a></h2>
<p>First we fetch a complete set of source files from our Fork...</p>
<pre><code class="language-yaml">    steps:
    - uses: actions/checkout@v2
</code></pre>
<p>The <a href="https://docs.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow#using-the-checkout-action"><code>actions/checkout</code></a> GitHub Action copies the source files into <code>/home/runner/work/Pinetime/Pinetime</code></p>
<h2 id="check-cache-for-emscripten" class="section-header"><a href="#check-cache-for-emscripten">9.2 Check Cache for emscripten</a></h2>
<p>Our Ubuntu Virtual Machine in the GitHub Cloud is based on the Intel x64 platform... But we're compiling our C and C++ program to WebAssembly.</p>
<p>To do that, we need to install the <a href="https://emscripten.org/index.html"><strong>emscripten WebAssembly Compiler</strong></a></p>
<p>We'll install this in the next step, but first we check whether emscripten is in our cache...</p>
<pre><code class="language-yaml">    - name: Check cache for emscripten
      id:   cache-emsdk
      uses: actions/cache@v2
      env:
        cache-name: cache-emsdk
      with:
        path: /tmp/emsdk
        key:  ${{ runner.os }}-build-${{ env.cache-name }}
        restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}
</code></pre>
<p><em>Why cache the Embedded Arm Toolchain?</em></p>
<p>emscripten is a huge 90 MB download (compressed).</p>
<p>Every time GitHub builds our firmware, it creates a fresh new empty Virtual Machine.</p>
<p>(So that our firmware builds may be reproduced consistently... And for security too)</p>
<p>GitHub will take roughly 30 seconds to download and unpack emscripten... Unless we cache it.</p>
<pre><code class="language-yaml">    - name: Check cache for emscripten
      id:   cache-emsdk
      uses: actions/cache@v2
</code></pre>
<p>The <a href="https://docs.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows"><code>actions/cache</code></a> GitHub Action lets us cache emscripten for future builds.</p>
<p>We can have multiple caches. Here's our cache for emscripten...</p>
<pre><code class="language-yaml">      env:
        cache-name: cache-emsdk
</code></pre>
<p>Next we tell GitHub what to cache...</p>
<pre><code class="language-yaml">      with:
        path: /tmp/emsdk
        key:  ${{ runner.os }}-build-${{ env.cache-name }}
        restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}
</code></pre>
<p>Given these build settings...</p>
<pre><code class="language-bash">runner.os      = Linux
env.cache-name = cache-emsdk
</code></pre>
<p>This means...</p>
<ul>
<li>
<p>GitHub shall cache the temporary emscripten folder <code>/tmp/emsdk</code></p>
<p>(We'll download emscripten to this folder in the next step)</p>
</li>
<li>
<p>The unique cache key for our toolchain cache shall be <code>Linux-build-cache-emsdk</code></p>
</li>
<li>
<p>In future builds, GitHub shall attempt to restore the cache for <code>Linux-build-cache-emsdk</code> into our emscripten folder <code>/tmp/emsdk</code></p>
</li>
</ul>
<h2 id="install-emscripten" class="section-header"><a href="#install-emscripten">9.3 Install emscripten</a></h2>
<p>Now we download and unpack emscripten into the temporary folder <code>/tmp/emsdk</code>...</p>
<pre><code class="language-yaml">    - name: Install emscripten
      if:   steps.cache-emsdk.outputs.cache-hit != 'true'  # Install emscripten if not found in cache
      run:  |
        # Based on https://emscripten.org/docs/getting_started/downloads.html
        cd /tmp

        # Get the emsdk repo
        git clone https://github.com/emscripten-core/emsdk.git

        # Enter that directory
        cd emsdk

        # Download and install the latest SDK tools.
        ./emsdk install latest

        # Make the &quot;latest&quot; SDK &quot;active&quot; for the current user. (writes .emscripten file)
        ./emsdk activate latest

        # Activate PATH and other environment variables in the current terminal
        source ./emsdk_env.sh

        # Show version
        emcc --version
        emcc --version        
</code></pre>
<p><em>Why is there a condition for the step?</em></p>
<pre><code class="language-yaml">      # Install emscripten if not found in cache
      if:   steps.cache-emsdk.outputs.cache-hit != 'true'  
</code></pre>
<p>This says that GitHub shall download emscripten only if the previous step <code>cache-emsdk</code> couldn't find an existing cache for emscripten.</p>
<p>Huge downloads and reinstallation averted... So neat!</p>
<p><em>What software is preinstalled on the GitHub Virtual Machine?</em></p>
<p><a href="https://github.com/actions/virtual-environments/blob/ubuntu18/20200726.1/images/linux/Ubuntu1804-README.md">Check out the preinstalled software on Ubuntu 18.04 for GitHub Actions</a></p>
<h2 id="check-cache-for-wabt" class="section-header"><a href="#check-cache-for-wabt">9.4 Check Cache for wabt</a></h2>
<p>Now that we have installed and cached emscripten, let's do the same for <a href="https://github.com/WebAssembly/wabt">wabt, the WebAssembly Binary Toolkit</a></p>
<p>First we check the cache for wabt...</p>
<pre><code class="language-yaml">    - name: Check cache for wabt
      id:   cache-wabt
      uses: actions/cache@v2
      env:
        cache-name: cache-wabt
      with:
        path: /tmp/wabt
        key:  ${{ runner.os }}-build-${{ env.cache-name }}
        restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}
</code></pre>
<h2 id="install-wabt" class="section-header"><a href="#install-wabt">9.5 Install wabt</a></h2>
<p>Then we install wabt in <code>/tmp/wabt</code>...</p>
<pre><code class="language-yaml">    - name: Install wabt
      if:   steps.cache-wabt.outputs.cache-hit != 'true'  # Install wabt if not found in cache
      run:  |
        cd /tmp
        git clone --recursive https://github.com/WebAssembly/wabt
        cd wabt
        mkdir build
        cd build
        cmake ..
        cmake --build .
</code></pre>
<h2 id="checkout-lvgl-for-webassembly" class="section-header"><a href="#checkout-lvgl-for-webassembly">9.6 Checkout LVGL for WebAssembly</a></h2>
<p>Now it gets interesting. Here we fetch the source code from <code>lvgl-wasm</code>...</p>
<pre><code class="language-yaml">    - name: Checkout LVGL for WebAssembly
      run:  |
        cd /tmp
        git clone https://github.com/AppKaki/lvgl-wasm
</code></pre>
<p>And save it to <code>/tmp/lvgl-wasm</code></p>
<p><em>What's <code>lvgl-wasm</code>?</em></p>
<p>PineTime Web Simulator runs in a Web Browser based on WebAssembly (somewhat similar to Java Applets). <a href="https://developer.mozilla.org/en-US/docs/WebAssembly/Concepts">More about WebAssembly</a></p>
<p><code>Clock.cpp</code> is our C++ class that contains the Watch Face code. <code>Clock.cpp</code> calls functions from two providers...</p>
<ol>
<li>
<p><a href="https://docs.lvgl.io/latest/en/html/index.html">LVGL UI Toolkit Library</a></p>
</li>
<li>
<p><a href="https://github.com/JF002/Pinetime">InfiniTime Operating System</a> based on FreeRTOS</p>
</li>
</ol>
<p>We have a version of LVGL compiled for WebAssembly... It's inside <code>lvgl-wasm</code>...</p>
<p><a href="https://github.com/AppKaki/lvgl-wasm"><code>github.com/AppKaki/lvgl-wasm</code></a></p>
<p>So we'll be compiling <code>lvgl-wasm</code> to WebAssembly together with our Watch Face code.</p>
<p><em>What about the InfiniTime Operating System?</em></p>
<p>Our PineTime Web Simulator doesn't support all functions provided by InfiniTime... <code>lvgl-wasm</code> simulates the minimal set of InfiniTime functions needed for rendering Watch Faces. (FreeRTOS is not supported by the Simulator)</p>
<p>Hence <code>lvgl-wasm</code> works like a <strong>Sandbox</strong>. We'll learn more details in the <a href="https://github.com/AppKaki/lvgl-wasm"><code>lvgl-wasm</code> documentation</a></p>
<h2 id="copy-watch-face-clockcpp-to-lvgl-for-webassembly" class="section-header"><a href="#copy-watch-face-clockcpp-to-lvgl-for-webassembly">9.7 Copy Watch Face Clock.cpp to LVGL for WebAssembly</a></h2>
<p>Remember that <code>lvgl-wasm</code> is just a Sandbox for simulating Watch Faces... It needs the actual Watch Face code.</p>
<p>Here's how we copy the Watch Face code in <code>Clock.cpp</code> to <code>lvgl-wasm</code>...</p>
<pre><code class="language-yaml">    - name: Copy Watch Face Clock.cpp to LVGL for WebAssembly
      run:  |
        cp src/DisplayApp/Screens/Clock.cpp /tmp/lvgl-wasm/clock
</code></pre>
<h2 id="build-lvgl-for-webassembly" class="section-header"><a href="#build-lvgl-for-webassembly">9.8 Build LVGL for WebAssembly</a></h2>
<p>Now that the Watch Face code is inside <code>lvgl-wasm</code>, let's build the project with emscripten...</p>
<pre><code class="language-yaml">    - name: Build LVGL for WebAssembly
      run:  |
        # Add emscripten and wabt to the PATH
        source /tmp/emsdk/emsdk_env.sh
        export PATH=$PATH:/tmp/wabt/build

        # Build LVGL app: wasm/lvgl.html, lvgl.js, lvgl.wasm
        cd /tmp/lvgl-wasm
        wasm/lvgl.sh
</code></pre>
<p><code>lvgl.sh</code> shall be explained in the <a href="https://github.com/AppKaki/lvgl-wasm"><code>lvgl-wasm</code> documentation</a></p>
<p>The script calls emscripten to generate three files in <code>/tmp/lvgl-wasm/wasm/</code>...</p>
<ul>
<li>
<p><code>lvgl.wasm</code>: WebAssembly Executable Code, containing our Watch Face, LVGL and the InfiniTime Sandbox. <a href="https://github.com/lupyuen/pinetime-lab/blob/master/docs/lvgl.wasm">Sample File</a></p>
</li>
<li>
<p><code>lvgl.js</code>: Provides the JavaScript glue that's needed to load <code>lvgl.wasm</code> and run it in a Web Browser. <a href="https://github.com/lupyuen/pinetime-lab/blob/master/docs/lvgl.js">Sample File</a></p>
</li>
<li>
<p><code>lvgl.html</code>: The HTML file that calls <code>lvgl.js</code> to render the user interface.</p>
<p>We won't be using this file, because we have a <a href="https://github.com/lupyuen/pinetime-lab/blob/master/docs/lvgl.html">custom version of <code>lvgl.html</code></a></p>
</li>
</ul>
<h2 id="copy-webassembly-to-github-pages" class="section-header"><a href="#copy-webassembly-to-github-pages">9.9 Copy WebAssembly to GitHub Pages</a></h2>
<p>Next we copy the WebAssembly files to the <code>docs</code> folder, which will be hosted on GitHub Pages...</p>
<pre><code class="language-yaml">    - name: Copy WebAssembly to GitHub Pages
      run:  |
        if [ ! -d docs ]; then
          mkdir docs
        fi
        export src=/tmp/lvgl-wasm
        export docs=$src/docs
        export wasm=$src/wasm
        cp \
          $docs/index.md \
          $docs/lvgl.html \
          $wasm/*.html \
          $wasm/*.js \
          $wasm/*.wasm \
          $wasm/*.txt \
          docs
</code></pre>
<h2 id="commit-github-pages" class="section-header"><a href="#commit-github-pages">9.10 Commit GitHub Pages</a></h2>
<p>Finally we Commit the changed files in <code>docs</code> back to the Fork so that GitHub Pages will be updated...</p>
<pre><code class="language-yaml">    - name: Commit GitHub Pages
      uses: EndBug/add-and-commit@v4.4.0
      with:
        add: docs
</code></pre>
<p><a href="https://github.com/EndBug/add-and-commit">More about <code>add-and-commit</code></a></p>
<h2 id="upload-outputs" class="section-header"><a href="#upload-outputs">9.11 Upload Outputs</a></h2>
<p>For troubleshooting, we publish the generated WebAssembly files as an Artifact <code>wasm</code>...</p>
<pre><code class="language-yaml">    - name: Upload Outputs
      uses: actions/upload-artifact@v2
      with:
        name: wasm
        path: |
          /tmp/lvgl-wasm/wasm/*.html
          /tmp/lvgl-wasm/wasm/*.js
          /tmp/lvgl-wasm/wasm/*.wasm
          /tmp/lvgl-wasm/wasm/*.txt
</code></pre>
<h2 id="show-files" class="section-header"><a href="#show-files">9.12 Show Files</a></h2>
<p>Let's take a peek at the environment variables and the files that have been checked out...</p>
<pre><code class="language-yaml">    - name: Show files
      run:  set ; pwd ; ls -l /tmp/lvgl-wasm
</code></pre>
<p>The current directory <code>pwd</code> is shown as...</p>

<div class="example-wrap"><pre class="rust rust-example-rendered">
<span class="op">/</span><span class="ident">home</span><span class="op">/</span><span class="ident">runner</span><span class="op">/</span><span class="ident">work</span><span class="op">/</span><span class="ident">Pinetime</span><span class="op">/</span><span class="ident">Pinetime</span></pre></div>
<p>Check the section &quot;Environment Variables&quot; below for the complete list of environment variables.</p>
<h2 id="caching-at-the-end" class="section-header"><a href="#caching-at-the-end">9.13 Caching At The End</a></h2>
<p>Here's a tip about the caches we have created for emscripten and wabt...</p>
<p><strong>The files get cached only if the build succeeds</strong></p>
<p>If the first few builds fail (say due to coding errors), the files will never get cached. And restarting the build becomes painfully slow.</p>
<p>Therefore it's good to tweak the Workflow to make sure that the first build always succeeds... Like commenting out the actions from <code>Build LVGL for WebAssembly</code> onwards.</p>
<p>Subsequent builds will be a lot faster with the caching.</p>
<p>And that's how we build PineTime Simulator in the Cloud!</p>
<p><a href="https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions">GitHub Actions Workflow Syntax</a></p>
<h1 id="environment-variables" class="section-header"><a href="#environment-variables">10 Environment Variables</a></h1>
<p>TODO</p>
<p>This step in our GitHub Actions Workflow...</p>
<pre><code class="language-yaml">    - name: Show files
      run:  set ; pwd ; ls -l /tmp/lvgl-wasm
</code></pre>
<p>Shows these environment variables...</p>
<pre><code class="language-bash">AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache
ANDROID_HOME=/usr/local/lib/android/sdk
ANDROID_SDK_ROOT=/usr/local/lib/android/sdk
ANT_HOME=/usr/share/ant
AZURE_EXTENSION_DIR=/opt/az/azcliextensions
BASH=/bin/bash
lquote:extquote:force_fignore:hostcomplete:interactive_comments:progcomp:p
BASH_ALIASES=()
BASH_ARGC=()
BASH_ARGV=()
BASH_CMDS=()
BASH_LINENO=([0]=&quot;0&quot;)
BASH_SOURCE=([0]=&quot;/home/runner/work/_temp/a3bba1d.sh&quot;)
BASH_VERSINFO=([0]=&quot;4&quot; [1]=&quot;4&quot; [2]=&quot;20&quot; [3]=&quot;1&quot; [4]=&quot;release&quot; [5]
BASH_VERSION='4.4.20(1)-release'
BOOST_ROOT_1_69_0=/opt/hostedtoolcache/boost/1.69.0/x64
BOOST_ROOT_1_72_0=/opt/hostedtoolcache/boost/1.72.0/x64
CHROMEWEBDRIVER=/usr/local/share/chrome_driver
CHROME_BIN=/usr/bin/google-chrome
CI=true
CONDA=/usr/share/miniconda
DEBIAN_FRONTEND=noninteractive
DEPLOYMENT_BASEPATH=/opt/runner
DIRSTACK=()
DOTNET_NOLOGO='&quot;1&quot;'
DOTNET_SKIP_FIRST_TIME_EXPERIENCE='&quot;1&quot;'
EUID=1001
GECKOWEBDRIVER=/usr/local/share/gecko_driver
GITHUB_ACTION=run2
GITHUB_ACTIONS=true
GITHUB_ACTOR=lupyuen
GITHUB_API_URL=https://api.github.com
GITHUB_BASE_REF=
GITHUB_EVENT_NAME=push
GITHUB_EVENT_PATH=/home/runner/work/_temp/_github_workflow/event.json
GITHUB_GRAPHQL_URL=https://api.github.com/graphql
GITHUB_HEAD_REF=
GITHUB_JOB=build
GITHUB_REF=refs/heads/master
GITHUB_REPOSITORY=AppKaki/Pinetime
GITHUB_REPOSITORY_OWNER=AppKaki
GITHUB_RUN_ID=183212738
GITHUB_RUN_NUMBER=2
GITHUB_SERVER_URL=https://github.com
GITHUB_SHA=bce10a451e6cef08c30b1d6ac297e1f50cf57bf3
GITHUB_WORKFLOW='Build PineTime Firmware'
GITHUB_WORKSPACE=/home/runner/work/Pinetime/Pinetime
GOROOT=/opt/hostedtoolcache/go/1.14.4/x64
GOROOT_1_11_X64=/opt/hostedtoolcache/go/1.11.13/x64
GOROOT_1_12_X64=/opt/hostedtoolcache/go/1.12.17/x64
GOROOT_1_13_X64=/opt/hostedtoolcache/go/1.13.12/x64
GOROOT_1_14_X64=/opt/hostedtoolcache/go/1.14.4/x64
GRADLE_HOME=/usr/share/gradle
GROUPS=()
HOME=/home/runner
HOMEBREW_CELLAR='&quot;/home/linuxbrew/.linuxbrew/Cellar&quot;'
HOMEBREW_PREFIX='&quot;/home/linuxbrew/.linuxbrew&quot;'
HOMEBREW_REPOSITORY='&quot;/home/linuxbrew/.linuxbrew/Homebrew&quot;'
HOSTNAME=fv-az20
HOSTTYPE=x86_64
IFS=$' \t\n'
INVOCATION_ID=cc632305776e4c49848d4644a457d167
ImageOS=ubuntu18
ImageVersion=20200717.1
JAVA_HOME=/usr/lib/jvm/adoptopenjdk-8-hotspot-amd64
JAVA_HOME_11_X64=/usr/lib/jvm/adoptopenjdk-11-hotspot-amd64
JAVA_HOME_12_X64=/usr/lib/jvm/adoptopenjdk-12-hotspot-amd64
JAVA_HOME_7_X64=/usr/lib/jvm/zulu-7-azure-amd64
JAVA_HOME_8_X64=/usr/lib/jvm/adoptopenjdk-8-hotspot-amd64
JOURNAL_STREAM=9:31251
LANG=C.UTF-8
LEIN_HOME=/usr/local/lib/lein
LEIN_JAR=/usr/local/lib/lein/self-installs/leiningen-2.9.4-standalone.jar
M2_HOME=/usr/share/apache-maven-3.6.3
MACHTYPE=x86_64-pc-linux-gnu
OPTERR=1
OPTIND=1
OSTYPE=linux-gnu
PATH=/home/runner/work/_temp/arm-none-eabi/bin:/home/runner/work/_temp/-x86_64/bin/:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/in:/home/runner/.config/composer/vendor/bin:/home/runner/.dotnet/tools://local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
PERFLOG_LOCATION_SETTING=RUNNER_PERFLOG
POWERSHELL_DISTRIBUTION_CHANNEL=GitHub-Actions-ubuntu18
PPID=2451
PS4='+ '
PWD=/home/runner/work/Pinetime/Pinetime
RUNNER_OS=Linux
RUNNER_PERFLOG=/home/runner/perflog
RUNNER_TEMP=/home/runner/work/_temp
RUNNER_TOOL_CACHE=/opt/hostedtoolcache
RUNNER_TRACKING_ID=github_3a45354c-437f-42c1-b8fb-cff7fa3cf2a0
RUNNER_USER=runner
RUNNER_WORKSPACE=/home/runner/work/Pinetime
SELENIUM_JAR_PATH=/usr/share/java/selenium-server-standalone.jar
SHELL=/bin/bash
SHELLOPTS=braceexpand:errexit:hashall:interactive-comments
SHLVL=1
SWIFT_PATH=/usr/share/swift/usr/bin
TERM=dumb
UID=1001
USER=runner
VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg
_=/bin/bash
</code></pre>

    
</body>
</html>